-
Almost all frontend frameworks are syntax/ergonomic sugar on top of native JS and the browser.
-
None add fundamentally new runtime capabilities that the browser can’t already do.
-
.
-
No JS framework suddenly gives threads, parallel execution, or new DOM APIs.
-
They cannot extend what the browser can do.
-
Why frameworks exist :
-
They reduce repetitive wiring for large apps.
-
They integrate state, DOM updates, and async requests in a structured way.
-
They handle edge cases (race conditions, lifecycle, error states, component reuse) that become messy in raw JS.
-
Automate common patterns
-
Reduce bugs and boilerplate
-
Provide a declarative programming model
-
Comparisons
-
Reactivity :
-
React/Preact use component-level state and rerender subtree. Solid uses signals; updates are localized. Qwik uses signals/refs but focuses on serializing and lazy event handlers.
-
-
Rendering :
-
React/Preact VDOM diff. Solid direct DOM ops (compiled). Qwik renders on server then resumes only what’s needed.
-
-
Hydration :
-
React/Preact full or partial hydration. Solid partial hydration techniques. Qwik aims for no traditional hydration — it resumes.
-
-
Bundle/runtime :
-
Preact < React (smaller). Solid small runtime for reactivity. Qwik pushes logic to server/serialized payload; runtime focuses on resuming.
-
-
Use case :
-
React/Preact good for heavy interactivity and library ecosystem. Solid for maximum update performance. Qwik for extreme lazy-load and fast TTFP.
-
Vanilla JS (Not a framework, I'm just comparing)
Characteristics
-
AJAX:
-
fetch()orXMLHttpRequest(older API).
-
-
Problems appear as you scale:
-
Multiple endpoints → repeated fetch calls.
-
Multiple components updating DOM → repeated
innerHTMLupdates. -
Error handling → repeated try/catch blocks.
-
State management → you must track which component has which data.
-
UI updates tied to async → manually coordinate loading states, spinners, etc.
-
Examples
-
More complex async behavior :
const widgets = [ { id: "widget1", url: "/data1" }, { id: "widget2", url: "/data2" }, { id: "widget3", url: "/data3" }, ]; const status = document.getElementById("status"); let completed = 0; widgets.forEach(w => { const el = document.getElementById(w.id); el.innerHTML = "Loading..."; fetch(w.url) .then(res => { if (!res.ok) throw new Error("Network error"); return res.text(); }) .then(html => { el.innerHTML = html; }) .catch(err => { el.innerHTML = "Error loading widget"; console.error(err); }) .finally(() => { completed++; if (completed === widgets.length) { status.innerHTML = "Dashboard loaded"; } }); });
Svelte
-
-
No Virtual DOM.
-
Works as a compiler, to turn the
.sveltecode into plain JS. -
Full TypeScript support (
.sveltefiles with<script lang="ts">) -
Compile-time type checking on props, stores, and events
-
1 component per file.
-
Every
.sveltefile has 3 parts:-
script + markup + style.
-
-
Very fast runtime, small bundle
-
AJAX:
-
Fetch or
loadfunctions use AJAX under the hood.
-
Examples
-
Button increases a counter :
<script>
let count = 0;
const increment = () => count++;
</script>
<p>Count: {count}</p>
<button on:click={increment}>Increment</button>
-
Ex2 :
<script>
export let items = [];
</script>
{#if items.length}
<ul>
{#each items as item}
<li>{item.title}</li>
{/each}
</ul>
{:else}
<div>No items</div>
{/if}
Qwik
-
.
Solid
-
No Virtual DOM.
-
Similar to React.
-
Uses JSX.
-
Kinda like React, but with a Svelte-like compiler
-
"A more well thought-out and faster version of React".
Examples
-
Button increases a counter :
import { createSignal } from 'https://cdn.skypack.dev/solid-js';
import { render } from 'https://cdn.skypack.dev/solid-js/web';
function Counter() {
const [count, setCount] = createSignal(0);
return (
<div>
<p>Count: {count()}</p>
<button onClick={() => setCount(count() + 1)}>Increment</button>
</div>
);
}
render(() => <Counter >, document.getElementById('app'));
-
Ex2 :
import { For, Show } from "solid-js";
export default function List(props) {
return (
<Show when={props.items.length} fallback={<div>No items</div>}>
<ul>
<For each={props.items}>{item => <li>{item.title}</li>}</For>
</ul>
</Show>
);
}
import { For } from "solid-js";
export default function Users(props) {
return <ul><For each={props.users}>{u => <li>{u.name}</li>}</For></ul>;
}
Vue
-
-
React's components, Angular's templates.
-
A
.vuefile is:-
Template + Script + Style
-
Characteristics
-
Has Virtual DOM.
-
AJAX:
-
Vue itself doesn’t do AJAX, but most Vue apps use it for components.
-
Examples
-
Button increases a counter :
<div id="app">
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
<script type="module">
import { createApp, ref } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';
const App = {
setup() {
const count = ref(0);
const increment = () => count.value++;
return { count, increment };
}
};
createApp(App).mount('#app');
</script>
-
Ex2 :
<template>
<div>
<ul v-if="items.length">
<li v-for="item in items" :key="item.id">{{ item.title }}</li>
</ul>
<div v-else>No items</div>
</div>
</template>
<script setup>
defineProps({ items: Array });
</script>
Preact
-
React-compatible API, smaller runtime.
-
Has Virtual DOM.
-
Uses JSX.
Examples
-
Button increases a counter :
import { h, render } from 'https://unpkg.com/preact@latest?module';
import { useState } from 'https://unpkg.com/preact@latest/hooks/dist/hooks.mjs?module';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
render(<Counter >, document.getElementById('app'));
React
-
From Facebook, 2013.
-
Uses JSX.
-
Has Virtual DOM.
-
Svelte or Solid sounds much better than this thing.
-
AJAX:
-
Via
fetch, Axios, or libraries like React Query; client-side rendering is driven by async data.
-
Asynchronous Behavior
-
React doesn’t fundamentally change how concurrency works—JS is still single-threaded—but it improves ergonomics and state management around asynchronous operations
-
React doesn’t add new concurrency primitives—it doesn’t give JS threads, parallelism, or async I/O that didn’t exist.
-
Everything React does is built on native JS: Promises, the event loop,
fetch, timers, etc. -
Automatic DOM diffing → no manual
innerHTMLmanipulation.-
Your component re-renders automatically when state changes.
-
In Vanilla JS :
fetch("/data") .then(res => res.text()) .then(html => document.getElementById("widget").innerHTML = html);-
You must track which element corresponds to which data.
-
Any additional async operations updating the same element require careful coordination.
-
-
In React :
const [data, setData] = useState(null); useEffect(() => { fetch("/data") .then(res => res.text()) .then(setData); }, []); return <div>{data || "Loading..."}</div>;-
React automatically re-renders the component when state changes.
-
No manual DOM manipulation.
-
Concurrency conflicts are minimized because state updates are batched and applied in order.
-
-
Examples
-
Button increases a counter :
import { useState } from "react";
import { createRoot } from "react-dom/client";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
const root = createRoot(document.getElementById("app"));
root.render(<Counter >);
-
Simple async behavior :
function Component() {
const [data, setData] = React.useState(null);
React.useEffect(() => {
fetch("/data")
.then(res => res.json())
.then(setData);
}, []);
return <div>{data}</div>;
}
-
More complex async behavior :
function Dashboard() { const [widgets, setWidgets] = React.useState([]); const [loaded, setLoaded] = React.useState(false); React.useEffect(() => { Promise.all([ fetch("/data1").then(r => r.text()), fetch("/data2").then(r => r.text()), fetch("/data3").then(r => r.text()), ]) .then(results => setWidgets(results)) .finally(() => setLoaded(true)); }, []); return ( <div> {widgets.map((w, i) => ( <div key={i}>{w || "Loading..."}</div> ))} {loaded && <div>Dashboard loaded</div>} </div> ); }
Angular
-
Google, 2010.
-
Angular 2 in 2016.
-
Requires TS.
Characteristics
-
Has Virtual DOM.
-
Reactivity model: Change detection.
-
AJAX:
-
Has built-in
HttpClientfor AJAX requests; components update via async data.
-
-
Template syntax, but bound tightly to TypeScript logic.
// user-list.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'user-list',
template: `
<ul>
<li *ngFor="let u of users">{{ u.name }}</li>
</ul>
`
})
export class UserListComponent {
users = [{ id:1, name:'Alice' }, { id:2, name:'Bob' }];
}
Examples
-
Button increases a counter :
// counter.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<p>Count: {{ count }}</p>
<button (click)="increment()">Increment</button>
`
})
export class CounterComponent {
count = 0;
increment() { this.count++; }
}
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { CounterComponent } from './counter.component';
@NgModule({
declarations: [CounterComponent],
imports: [BrowserModule],
bootstrap: [CounterComponent]
})
export class AppModule {}
Backbone
Ember
-
-
Convention over configuration.
Lit
-
Google.
-
Focused on Web Components.
Stencil
-
Focused on Web Components.
Mithril
-
Virtual DOM.